﻿using System;
using System.Diagnostics;
using System.Net;
using System.Windows.Threading;

using BlackJack.Base.Multicast;
using BlackJack.Base.Commands;
using BlackJack.Infrastructure.Helpers;
using BlackJack.Multicast.Udp;

namespace BlackJack.Multicast
{
    public class Gateway
        : IGateway
    {
        #region Events

        public event EventHandler Joined;

        public event EventHandler BeforeClose;

        public event EventHandler<CommandEventArgs> CommandRecieved;

        #endregion

        #region Members

        const string DataDelimeter = "|";
        const string DataFormat = "{0}" + DataDelimeter + "{1}";

        const string GROUP_ADDRESS = "224.0.1.11";
        const int GROUP_PORT = 54329;

        readonly ICommand<object> _readyCommand = new Command<object>(CommandGame.Ready, null);

        UdpAnySourceMulticastChannel _channel;

        DispatcherTimer _dt;

        bool _isJoined;

        #endregion        

        #region Constructor

        public Gateway()
        {
            _channel = new UdpAnySourceMulticastChannel(GROUP_ADDRESS, GROUP_PORT);

            RegisterEvents();

            StartKeepAlive();
        }

        #endregion

        #region Initializers

        void RegisterEvents()
        {
            _channel.Joined += new EventHandler(Channel_Joined);
            _channel.BeforeClose += new EventHandler(Channel_BeforeClose);
            _channel.PacketReceived += new EventHandler<UdpPacketReceivedEventArgs>(Channel_PacketReceived);
        }

        #endregion

        #region Finalizers

        void UnregisterEvents()
        {
            if (_channel != null)
            {
                _channel.Joined -= new EventHandler(Channel_Joined);
                _channel.BeforeClose -= new EventHandler(Channel_BeforeClose);
                _channel.PacketReceived -= new EventHandler<UdpPacketReceivedEventArgs>(Channel_PacketReceived);
            }
        }

        #endregion

        #region Multicast Communication

        public void Send<T>(ICommand<T> command)
        {
            if (_channel != null && _isJoined)
            {
                var json = command.ToJSON();

                var message = string.Format(DataFormat, typeof(ICommand<T>).FullName, json);

                _channel.Send(message);
            }
        }

        #endregion

        #region Multicast Communication Handlers

        void Channel_Joined(object sender, EventArgs e)
        {
            _isJoined = true;

            OnJoined();
        }

        void Channel_BeforeClose(object sender, EventArgs e)
        {
            OnBeforeClose();
        }

        void Channel_PacketReceived(object sender, UdpPacketReceivedEventArgs e)
        {
            var message = e.Message.Split(DataDelimeter.ToCharArray());

            var type = Type.GetType(message[0]) ;
            var command = message[1].FromJSON(type) as ICommand;

            if (command != null)
            {
                if (command.Action != CommandGame.Ready)
                    OnCommandRecieved(command, e.Source);
                else
                    Debug.WriteLine("Ready received");
            }
        }

        #endregion

        #region Keep-Alive

        void StartKeepAlive()
        {
            if (_dt == null)
            {
                _dt = new DispatcherTimer();
                _dt.Interval = new TimeSpan(0, 0, 1);
                _dt.Tick += (s, args) =>
                            {
                                Send(_readyCommand);
                            };
            }

            _dt.Start();
        }

        void StopkeepAlive()
        {
            if (_dt != null)
                _dt.Stop();
        }

        #endregion

        #region Event Callers

        void OnJoined()
        {
            var handler = Joined;

            if (handler != null)
                handler(this, null);
        }

        void OnBeforeClose()
        {
            var handler = BeforeClose;

            if (handler != null)
                handler(this, null);
        }

        void OnCommandRecieved(ICommand command, IPEndPoint source)
        {
            var handler = CommandRecieved;

            if (handler != null)
                handler(this, new CommandEventArgs(command, source));
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            UnregisterEvents();
            StopkeepAlive();
        }

        #endregion
    }
}
